﻿using System;
using System.Collections.Generic;
using System.Text;

using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Core;
using System.IO;

namespace SVN.SourceCode.BackUp
{

    public class ZipUtil
    {
        public string CreateZipFile(string folderToZip, string outputZipFilePath = "", string password = "")
        {

            if (Directory.Exists(folderToZip) == false)
                throw new FileNotFoundException("folderToZip file not found", folderToZip);

            if (outputZipFilePath == null)
                throw new ArgumentNullException("outputZipFilePath");


            outputZipFilePath = GetNewZipFileName(outputZipFilePath.Trim().Length == 0 ? folderToZip : outputZipFilePath);

            FileStream fsOut = File.Create(outputZipFilePath);
            ZipOutputStream zipStream = new ZipOutputStream(fsOut);

            zipStream.SetLevel(3); //0-9, 9 being the highest level of compression

            zipStream.Password = password;	// optional. Null is the same as not setting.


            // This setting will strip the leading part of the folder path in the entries, to
            // make the entries relative to the starting folder.
            // To include the full path for each entry up to the drive root, assign folderOffset = 0.
            if (folderToZip.Trim() != "")
            {
                int folderOffset = folderToZip.Length + (folderToZip.EndsWith("\\") ? 0 : 1);
                CompressFolder(folderToZip, zipStream, folderOffset);
            }
            zipStream.IsStreamOwner = true;	// Makes the Close also Close the underlying stream
            zipStream.Close();

            return outputZipFilePath;
        }

        private static string GetNewZipFileName(string outputZipFilePath)
        {
            string tempoutputZipFilePath = Path.HasExtension(outputZipFilePath) ? outputZipFilePath :
                                            (outputZipFilePath + ".zip");

            string tempDir = Path.GetDirectoryName(tempoutputZipFilePath);
            string tempFileName = Path.GetFileNameWithoutExtension(tempoutputZipFilePath);
            string tempExtension = Path.GetExtension(tempoutputZipFilePath);
            int count = 1;
            while (File.Exists(tempoutputZipFilePath))
            {
                tempoutputZipFilePath = Path.Combine(tempDir, tempFileName + count++ + tempExtension);
            }
            return tempoutputZipFilePath;
        }

        private void CompressFolder(string path, ZipOutputStream zipStream, int folderOffset)
        {

            string[] files = Directory.GetFiles(path);

            foreach (string filename in files)
            {

                FileInfo fi = new FileInfo(filename);

                string entryName = filename.Substring(folderOffset); // Makes the name in zip based on the folder
                entryName = ZipEntry.CleanName(entryName); // Removes drive from name and fixes slash direction
                ZipEntry newEntry = new ZipEntry(entryName);
                newEntry.DateTime = fi.LastWriteTime; // Note the zip format stores 2 second granularity

                // Specifying the AESKeySize triggers AES encryption. Allowable values are 0 (off), 128 or 256.
                //   newEntry.AESKeySize = 256;

                // To permit the zip to be unpacked by built-in extractor in WinXP and Server2003, WinZip 8, Java, and other older code,
                // you need to do one of the following: Specify UseZip64.Off, or set the Size.
                // If the file may be bigger than 4GB, or you do not need WinXP built-in compatibility, you do not need either,
                // but the zip will be in Zip64 format which not all utilities can understand.
                //   zipStream.UseZip64 = UseZip64.Off;
                newEntry.Size = fi.Length;

                zipStream.PutNextEntry(newEntry);

                // Zip the file in buffered chunks
                // the "using" will close the stream even if an exception occurs
                byte[] buffer = new byte[4096];
                using (FileStream streamReader = File.OpenRead(filename))
                {
                    StreamUtils.Copy(streamReader, zipStream, buffer);
                }
                zipStream.CloseEntry();
            }
            string[] folders = Directory.GetDirectories(path);
            foreach (string folder in folders)
            {
                CompressFolder(folder, zipStream, folderOffset);
            }

        }


    }
}
